package org.litespring.beans.factory.support;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.litespring.beans.BeanDefinition;
import org.litespring.beans.ConstructorArgument;
import org.litespring.beans.SimpleTypeConverter;
import org.litespring.beans.TypeMismatchException;
import org.litespring.beans.factory.BeanCreationException;

import java.lang.reflect.Constructor;
import java.util.List;

/**
 * @author ChangLiang
 * @date 2020/7/24
 */
public class ConstructorResolver {

    protected final Log logger = LogFactory.getLog(getClass());

    private final AbstractBeanFactory beanFactory;

    public ConstructorResolver(AbstractBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public Object autowireConstructor(final BeanDefinition bd) {
        Constructor<?> constructorToUse = null;
        Object[] argsToUse = null;

        Class<?> beanClass;

        // 这里这样写的效率并不高 应该在beanDefinition中缓存class load过的class 就缓存起来
        // 这里没有考虑效率 而是每一次都来装载 loadClass还是比较耗时的
        try {
            beanClass = this.beanFactory.getBeanClassLoader().loadClass(bd.getBeanClassName());
        } catch (ClassNotFoundException e) {
            throw new BeanCreationException(bd.getId(), "Instantiate of bean failed, can't resolve class", e);
        }

        Constructor<?>[] candidates = beanClass.getConstructors();
        ConstructorArgument constructorArgument = bd.getConstructorArgument();

        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory);
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();

        for (int i = 0; i < candidates.length; i++) {
            Class<?>[] parameterTypes = candidates[i].getParameterTypes();
            if (parameterTypes.length != constructorArgument.getArgumentCount()) {
                continue;
            }
            argsToUse = new Object[parameterTypes.length];
            boolean result = this.valuesMatchTypes(parameterTypes,
                    constructorArgument.getArgumentValues(),
                    argsToUse,
                    valueResolver,
                    typeConverter);
            if (result) {
                constructorToUse = candidates[i];
                break;
            }

        }
        if (constructorToUse == null) {
            throw new BeanCreationException(bd.getId(), "can't find an appropriate constructor");
        }
        try {
            return constructorToUse.newInstance(argsToUse);
        } catch (Exception e) {
            throw new BeanCreationException(bd.getId(), "can't create instance using " + constructorToUse);
        }
    }

    /**
     * check values if match constructor parameter types or not
     *
     * @param parameterTypes
     * @param valueHolders
     * @param argsToUse
     * @param valueResolver
     * @param typeConverter
     * @return
     */
    private boolean valuesMatchTypes(Class<?>[] parameterTypes,
                                     List<ConstructorArgument.ValueHolder> valueHolders,
                                     Object[] argsToUse,
                                     BeanDefinitionValueResolver valueResolver,
                                     SimpleTypeConverter typeConverter
    ) {
        for (int i = 0; i < parameterTypes.length; i++) {
            ConstructorArgument.ValueHolder valueHolder = valueHolders.get(i);
            // maybe RuntimeBeanReference or TypedStringValue
            Object originalValue = valueHolder.getValue();

            try {
                Object resolvedValue = valueResolver.resolveValueIfNecessary(originalValue);
                // 转型失败 抛出异常
                Object convertedValue = typeConverter.convertIfNecessary(resolvedValue, parameterTypes[i]);
                // 转型成功 记录下来
                argsToUse[i] = convertedValue;
            } catch (TypeMismatchException e) {
                logger.error(e);
                return false;
            }
        }
        return true;
    }
}
